#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @Time      :2021/7/5 10:38
# @Author    :cjw
import os
import socket
import subprocess
import time
import multiprocessing

from app_wework_po.util.log import logger
from app_wework_po.setting import LOG_PATH


class AppiumDeviceSync:
	"""
	appium服务和设备并发执行，暂时只做了windows10 安卓的功能
	"""
	now_time = time.strftime('%Y_%m_%d_%H_%M_%S')  # 格式化的当前时间
	now_date = time.strftime('%Y_%m_%d')  # 格式化的当前日期

	@classmethod
	def check_port(cls, port: int, host: str = '127.0.0.1') -> bool:
		"""
		端口检查，通过socket连接appium服务来判断端口是否已经被占用
		socket: https://docs.python.org/3.8/library/socket.html

		shutdown方法：禁止在一个Socket上进行数据的接收与发送。利用shutdown()函数使socket双向数据传输变为单向数据传输。
		shutdown()需要一个单独的参数，该参数表示了如何关闭socket
		参数
		0：表示禁止将来读
		1：表示禁止将来写
		2：表示禁止将来读和写
		:param port: 检查端口
		:param host: 检查地址，默认本地
		:return:
		"""
		# AF_INET: 使用ipv4协议通信
		s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		try:
			logger.info(f'开始进行端口检查，检查ip：{host}, 检查端口：{port}')
			s.connect((host, port))
			s.shutdown(2)  # 关闭连接
		except OSError as e:  # 连接出现异常，表示端口没有被占用
			logger.info(f'端口：{port}，未被使用')
			return True
		else:
			logger.info(f'端口：{port}，已经被使用')
			return False

	@classmethod
	def release_port(cls, port: int) -> None:
		"""
		端口释放
		:param port: 释放的端口
		:return:
		"""
		# 根据要释放的端口查找进程pid的命令
		find_command = f'netstat -ano | findstr {port}'
		logger.info(f'查找进程端口命令：{find_command}')
		# 执行获取结果
		result = os.popen(find_command).read()
		logger.info(f'进程pid查找结果：{result}')

		# 查找进程pid, 存在会有LISTENING 监听的pid
		if str(port) and 'LISTENING' in result:
			# 获取pid
			i = result.index('LISTENING')
			start = i + len('LISTENING') + 7
			end = result.index('\n')
			pid = result[start:end]
			logger.info(f'获取的进程pid：{pid}')

			# 释放端口对应的pid
			kill_command = f'taskkill -f -pid {pid}'
			logger.info(f'端口释放命令：{kill_command}')
			os.popen(kill_command)
			logger.info(f'端口：{port}，释放完成')
		else:
			logger.info(f'端口：{port}，是可用的')

	@classmethod
	def appium_start(cls, port: int, host: str = '0.0.0.0') -> None:
		"""
		启动appium服务，通过subprocess模块启动
		subprocess：https://docs.python.org/3.8/library/subprocess.html
		该模块可以创建新的进程，并且连接到进程的输入、输出、错误等管道信息，并且可以获取进程的返回值。

		启动参数：
		-bp(--bootstrap-port): appium和设备之间通信的端口，如果不指定到时无法操作多台设备运行脚本。
		--relaxed-security: 禁用其他安全检查
		--session-override: 启用会话覆盖（关闭）
		:param port: 启动端口
		:param host: 启动IP，默认是0.0.0.0
		:return:
		"""
		bootstrap_port = port + 1
		# 加上start /b 表示后台运行
		cmd = f'start /b appium -a {host} -p {port} -bp {bootstrap_port} ' \
		      f'--session-override --relaxed-security'
		logger.info(f'执行appium启动命令：{cmd}')

		# 动态生成日志文件，重启服务后每天生成一个日志文件
		log_path = os.path.join(LOG_PATH, 'appium', f'{port}')
		if not os.path.exists(log_path):
			os.mkdir(log_path)
		log_path = os.path.join(log_path, f'{cls.now_date}.log')

		# 执行命令，并把日志打印到指定的日志文件中
		logger.info(f'启动appium服务：http://{host}:{port}/wb/hub')
		subprocess.Popen(cmd, shell=True, stdout=open(log_path, 'a', encoding='utf-8'),
		                 stderr=subprocess.STDOUT, encoding='utf-8')

	@classmethod
	def appium_restart(cls, port: int, host: str = '0.0.0.0') -> None:
		"""
		重启appium服务
		:param port: 启动端口
		:param host: 启动IP，默认是0.0.0.0
		:return:
		"""
		logger.info(f'重启appium服务：http://{host}:{port}/wb/hub')
		cls.release_port(port)  # 释放端口
		cls.appium_start(port, host)  # 启动appium服务

	@classmethod
	def start_appium_action(cls, port: int, host: str = '0.0.0.0'):
		"""
		检测端口是否被占用，如果没有被占用则启动appium服务,否则重启appium服务
		:param port: 启动端口
		:param host: 启动IP，默认是0.0.0.0
		:return:
		"""
		if cls.check_port(port):
			cls.appium_start(port, host)
		else:
			cls.appium_restart(port, host)

	@classmethod
	def appium_start_sync(cls, host: str = '0.0.0.0'):
		"""
		并发启动appium服务
		:param host:
		:return:
		"""
		logger.info('============开始并发启动appium服务==============')

		# 创建appium进程组
		appium_process = []
		# 获取设备udid列表
		device_list = cls.devices()

		flag = False  # 启动标识
		if device_list:  # 判断是否并发启动
			# 加载appium进程组
			for i in range(len(device_list)):
				port = 4723 + 2 * i
				appium = multiprocessing.Process(target=cls.start_appium_action, args=(port, host))
				appium_process.append(appium)

			# 启动appium服务
			for appium in appium_process:
				appium.start()
			for appium in appium_process:
				appium.join()
			time.sleep(2)  # 给个时间缓冲一下
			logger.info('并发启动成功')
			flag = True
		else:
			logger.info('没有查找到任何设备，并发启动失败')
		logger.info('============结束并发启动appium服务==============')
		return flag

	@classmethod
	def devices(cls) -> list:
		"""获取当前电脑上连接成功的设备序列号列表"""
		# 获取执行设备查询命令的结果，去除首位两行
		result = os.popen(r'adb devices').readlines()[1:-1]
		# 过滤非device状态的设备序列号
		devices = [device for device in result if 'device' in device]
		return [device.split('\t')[0] for device in devices]


if __name__ == '__main__':
	ap = AppiumDeviceSync()
	# ap.appium_start_sync()
	ap.release_port(4723)
